// DirectDiskDlg.cpp : implementation file
//

#include "stdafx.h"
#include "DirectDisk.h"
#include "DirectDiskDlg.h"
#include <dbt.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

static LPCTSTR const_strLine =
_T("----------------------------------------------------------------------------------------------")
_T("----------------------------");
static LPCTSTR const_strLstHelp =
_T("Example of the image list file (.lst):\r\n")
_T(";file start_sector total_sectors\r\n")
_T("mbr.bin 0 1\r\n")
_T("nboot.bin 1 32");

//////////////////////////////////////////////////////////////////////////
struct THREADCALLBACK {
	CDirectDiskDlg*	pThis;
	PFNTHREAD pfnThread;
};

static UINT ThreadProc(LPVOID pParam)
{
	THREADCALLBACK *pCallback = (THREADCALLBACK*)pParam;
	ASSERT(pCallback);
	ASSERT(pCallback->pThis);
	ASSERT(pCallback->pfnThread);
	UINT nRet = (pCallback->pThis->*(pCallback->pfnThread))();
	delete pCallback;
	return nRet;
}
//////////////////////////////////////////////////////////////////////////

// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	enum { IDD = IDD_ABOUTBOX };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()


// CDirectDiskDlg dialog




CDirectDiskDlg::CDirectDiskDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CDirectDiskDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_bCancel = FALSE;
}

void CDirectDiskDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_PROGRESS, m_ctlProgress);
	DDX_Control(pDX, IDC_OUTPUT, m_editOutput);
	DDX_Control(pDX, IDC_IMGLST, m_cmbImgLst);
	DDX_Control(pDX, IDC_PHYDRV, m_cmbPhyDrv);
}

BEGIN_MESSAGE_MAP(CDirectDiskDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_DEVICECHANGE()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_READ, OnBnClickedRead)
	ON_BN_CLICKED(IDC_WRITE, OnBnClickedWrite)
	ON_BN_CLICKED(IDC_EJECT, OnBnClickedEject)
	ON_BN_CLICKED(IDC_BROWSE, OnBnClickedBrowse)
	ON_BN_CLICKED(IDC_RESCAN, OnBnClickedRescan)
	ON_CBN_SELCHANGE(IDC_PHYDRV, OnSelchangedPhyDrv)
END_MESSAGE_MAP()


// CDirectDiskDlg message handlers

BOOL CDirectDiskDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	m_editOutput.LimitText(-1);
	SetDefID(IDC_WRITE);
	OnBnClickedRescan();

	CString strFile = AfxGetApp()->GetProfileString(_T("Config"), _T("ImgLstFile"));
	m_cmbImgLst.SetWindowText(strFile);

	return TRUE;  // return TRUE  unless you set the focus to a control
}

void CDirectDiskDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CDirectDiskDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
		if (m_mbr.n55 == 0x55 && m_mbr.nAA == 0xAA) {
			CWnd *pWnd = GetDlgItem(IDC_PARTITION);
			CRect rect, rc;
			CWindowDC wdc(pWnd);
			const int MIN_WIDTH = 10;

			pWnd->GetClientRect(&rect);
			wdc.Rectangle(rect);
			rc = rect;

			if (!m_nTotalSectors)
				return;

			for (int i = 0; i < 4; i++) {
				rc.left = (ULONGLONG)m_mbr.Part[i].Part_StartSector * rect.Width() / m_nTotalSectors;
				if (i && rc.left <= rc.right)
					rc.left = rc.right + 1;
				rc.right = (ULONGLONG)m_mbr.Part[i].Part_TotalSectors * rect.Width() / m_nTotalSectors + rc.left;
				if (rc.Width() < MIN_WIDTH && m_mbr.Part[i].Part_TotalSectors)
					rc.right = rc.left + MIN_WIDTH;
				wdc.FillSolidRect(rc, RGB((i+1)*60, 100, 192));
			}
		}
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CDirectDiskDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

CWinThread* CDirectDiskDlg::CreateThread(PFNTHREAD pfnThread)
{
	// XXX: the pCallback will be deleted in ThreadProc
	THREADCALLBACK *pCallback = new THREADCALLBACK;
	pCallback->pThis = this;
	pCallback->pfnThread = pfnThread;
	return AfxBeginThread(ThreadProc, pCallback);
}

BOOL CDirectDiskDlg::WriteImage(LPCTSTR lpszPhyDrv, UINT nStartSector, LPCTSTR lpszImgFile)
{
	BOOL bRet = FALSE;
	CDisk disk(lpszPhyDrv);
	UINT nStartTime = GetTickCount();

	if (!disk.IsValid()) {
		OutputText(CString(lpszPhyDrv) + _T(" is invalid!\r\n"), COLOR_RED);
		return bRet;
	}

	if (!disk.SeekSector(nStartSector)) {
		CString strErr;
		strErr.Format(_T("SeekSector %d failed!\r\n"), nStartSector);
		OutputText(strErr, COLOR_RED);
		return bRet;
	}

	BYTE *pBuff = new BYTE[BUFF_SIZE];
	if (!pBuff) {
		OutputText(_T("Allocate Buffer Failed!"), COLOR_RED);
		return bRet;
	}

	CFile file;
	if (!file.Open(m_strImgPath + lpszImgFile, CFile::modeRead)) {
		OutputText(_T("Open Image File Failed!"), COLOR_RED);
		delete[] pBuff;
		return bRet;
	}

	CString strText;
	UINT nReadBytes, nReadSectors; /* per read */
	ULONGLONG nWrittenBytes = 0; /* total bytes written to disk */
	UINT nSectors = (UINT)(file.GetLength() + disk.GetSectorSize() - 1) / disk.GetSectorSize();

	m_ctlProgress.SetRange32(0, nSectors);
	m_ctlProgress.SetPos(0);
	m_ctlProgress.SetStep(1);
	while (TRUE) {
		if (m_bCancel) {
			OutputText(_T("Writting image cancelled!"), COLOR_RED);
			break;
		}

		nReadBytes = file.Read(pBuff, BUFF_SIZE);
		if (nReadBytes <= 0) {
			bRet = TRUE;
			break;
		}

		nReadSectors = (nReadBytes + disk.GetSectorSize() - 1) / disk.GetSectorSize();
		ASSERT(nReadSectors * disk.GetSectorSize() <= BUFF_SIZE);

		if (!disk.WriteSector(pBuff, nReadSectors)) {
			OutputText(_T("Write Image failed!"), COLOR_RED);
			break;
		}

		nWrittenBytes += nReadBytes;
		nSectors -= nReadSectors;

		if (GetTickCount() != nStartTime) {
			float fSpeed = (float)nWrittenBytes / (GetTickCount() - nStartTime); /* bytes/ms */
			float fTimeS = nSectors * disk.GetSectorSize() / fSpeed / 1000;
			strText.Format(_T("Windows DirectDisk 1.0 - %.1f MB/s, %d sectors (%.0f seconds) left"),
				fSpeed/1024, nSectors, fTimeS);
			SetWindowText(strText);
		}
		m_ctlProgress.SetPos(m_ctlProgress.GetPos() + nReadSectors);
	}

	file.Close();
	delete[] pBuff;
	return bRet;
}

// direct read data from disk to image file
BOOL CDirectDiskDlg::ReadImage(LPCTSTR lpszPhyDrv, UINT nStartSector, UINT nSectors, LPCTSTR lpszImgFile)
{
	BOOL bRet = FALSE;
	CDisk disk(lpszPhyDrv);
	UINT nStartTime = GetTickCount();

	if (!disk.IsValid()) {
		OutputText(CString(lpszPhyDrv) + _T(" is invalid!\r\n"), COLOR_RED);
		return bRet;
	}

	if (!disk.SeekSector(nStartSector)) {
		CString strErr;
		strErr.Format(_T("SeekSector %d failed!\r\n"), nStartSector);
		OutputText(strErr, COLOR_RED);
		return bRet;
	}

	CFileFind find;
	if (find.FindFile(m_strImgPath + lpszImgFile)) {
		CString strErr;
		strErr.Format(_T("The file %s already exists. Do you want to replace the existing file?"),
			m_strImgPath + lpszImgFile);
		if (MessageBox(strErr, 0, MB_YESNO) == IDNO) {
			OutputText(_T("Dumping image cancelled!"), COLOR_RED);
			find.Close();
			return bRet;
		}
	}
	find.Close();

	BYTE *pBuff = new BYTE[BUFF_SIZE];
	if (!pBuff) {
		OutputText(_T("Allocate Buffer Failed!"), COLOR_RED);
		return bRet;
	}

	CFile file;
	if (!file.Open(m_strImgPath + lpszImgFile, CFile::modeCreate|CFile::modeWrite)) {
		OutputText(_T("Create Image File Failed!"), COLOR_RED);
		delete[] pBuff;
		return bRet;
	}

	CString strText;
	UINT nReadSectors, nReadBytes; /* per read */
	ULONGLONG nWrittenBytes = 0; /* total bytes written to file */
	UINT nMaxSectors = BUFF_SIZE / disk.GetSectorSize();

	m_ctlProgress.SetRange32(0, nSectors);
	m_ctlProgress.SetPos(0);
	m_ctlProgress.SetStep(1);

	ASSERT(!(BUFF_SIZE % disk.GetSectorSize()));

	while (nSectors) {
		if (m_bCancel) {
			OutputText(_T("Dumping image cancelled!"), COLOR_RED);
			break;
		}

		nReadSectors = min(nSectors, nMaxSectors);
		if (!disk.ReadSector(pBuff, nReadSectors)) {
			OutputText(_T("Dumping image failed!"), COLOR_RED);
			break;
		}
		nReadBytes = nReadSectors * disk.GetSectorSize();

		file.Write(pBuff, nReadBytes);

		nSectors -= nReadSectors;
		nWrittenBytes += nReadBytes;

		if (GetTickCount() != nStartTime) {
			float fSpeed = (float)nWrittenBytes / (GetTickCount() - nStartTime); /* bytes/ms */
			float fTimeS = nSectors * disk.GetSectorSize() / fSpeed / 1000;
			strText.Format(_T("Windows DirectDisk 1.0 - %.1f MB/s, %d sectors (%.0f seconds) left"),
				fSpeed/1024, nSectors, fTimeS);
			SetWindowText(strText);
		}
		m_ctlProgress.SetPos(m_ctlProgress.GetPos() + nReadSectors);
	}

	file.Close();
	delete[] pBuff;
	return !nSectors;
}

/*
;file start_sector total_sectors
mbr.bin		0	1
nboot.bin 	1	32
uboot.bin	34	4096
uImage		4130	5120
root.img 	16065	610470
*/

BOOL CDirectDiskDlg::ReadAllImages()
{
	CStdioFile fLst;
	CString strLstFile, strText;
	UINT nStartTime = GetTickCount();
	CWnd *pWnd = GetDlgItem(IDC_READ);
	
	OutputText(_T("Dumping images..."));
	OutputText(const_strLine);

	m_cmbImgLst.GetWindowText(strLstFile);
	if (!fLst.Open(strLstFile, CFile::modeRead)) {
		OutputText(_T("Open image list file ")+strLstFile+_T(" failed!"), COLOR_RED);
		OutputText(const_strLstHelp);
		OutputText(const_strLine);
		pWnd->EnableWindow(TRUE);
		return FALSE;
	}

	int nScanned = 0;
	UINT nStartSector, nSectors;
	CString strLine, strImgFile, strPhyDrv;
	
	m_cmbPhyDrv.GetWindowText(strPhyDrv);
	pWnd->GetWindowText(strText);
	pWnd->SetWindowText(_T("Cancel"));
	pWnd->EnableWindow(TRUE);
	EnableAllBut(FALSE, IDC_READ);

	while (fLst.ReadString(strLine)) {
		nScanned = _stscanf(strLine.GetBuffer(MAX_PATH), _T("%s %d %d"),
			strImgFile.GetBuffer(MAX_PATH), &nStartSector, &nSectors);
		strLine.ReleaseBuffer();
		strImgFile.ReleaseBuffer();

		if (nScanned != 3) {
				OutputText(strLine, COLOR_RED);
				continue;
		} else {
			OutputText(strLine);
		}

		if (!ReadImage(strPhyDrv, nStartSector, nSectors, strImgFile))
			break;
	}
	fLst.Close();
	OutputText(const_strLine);
	strLine.Format(_T("Done in %d seconds"), (GetTickCount() - nStartTime)/1000);
	OutputText(strLine);
	pWnd->SetWindowText(strText);
	EnableAllBut(TRUE);
	return TRUE;
}

BOOL CDirectDiskDlg::WriteAllImages()
{
	CStdioFile fLst;
	CString strLstFile, strText;
	UINT nStartTime = GetTickCount();
	CWnd *pWnd = GetDlgItem(IDC_WRITE);

	OutputText(_T("Writing images..."));
	OutputText(const_strLine);

	m_cmbImgLst.GetWindowText(strLstFile);
	if (!fLst.Open(strLstFile, CFile::modeRead)) {
		OutputText(_T("Open image list file ")+strLstFile+_T(" failed!"), COLOR_RED);
		OutputText(const_strLstHelp);
		OutputText(const_strLine);
		pWnd->EnableWindow(TRUE);
		return FALSE;
	}

	int nScanned = 0;
	UINT nStartSector;
	CString strLine, strImgFile, strPhyDrv;

	m_cmbPhyDrv.GetWindowText(strPhyDrv);
	pWnd->GetWindowText(strText);
	pWnd->SetWindowText(_T("Cancel"));
	pWnd->EnableWindow(TRUE);
	EnableAllBut(FALSE, IDC_WRITE);

	while (fLst.ReadString(strLine)) {
		nScanned = _stscanf(strLine.GetBuffer(MAX_PATH), _T("%s %d"),
			strImgFile.GetBuffer(MAX_PATH), &nStartSector);
		strLine.ReleaseBuffer();
		strImgFile.ReleaseBuffer();
		if (nScanned != 2) {
				OutputText(strLine, COLOR_RED);
				continue;
		} else {
			OutputText(strLine);
		}

		if (!WriteImage(strPhyDrv, nStartSector, strImgFile))
			break;
	}
	fLst.Close();

	OutputText(const_strLine);
	strLine.Format(_T("Done in %d seconds"), (GetTickCount() - nStartTime)/1000);
	OutputText(strLine);
	pWnd->SetWindowText(strText);
	EnableAllBut(TRUE);
	return TRUE;
}

void CDirectDiskDlg::OnBnClickedRead()
{
	CString strText;
	CWnd *pWnd = GetDlgItem(IDC_READ);
	pWnd->GetWindowText(strText);

	pWnd->EnableWindow(FALSE);
	if (strText == _T("Cancel")) {
		m_bCancel = TRUE;
		return;
	}

	m_cmbImgLst.GetWindowText(strText);
	m_strImgPath = strText.Left(strText.ReverseFind(_T('\\')) + 1);
	m_bCancel = FALSE;
	CreateThread(&CDirectDiskDlg::ReadAllImages);
}

void CDirectDiskDlg::OnBnClickedWrite()
{
	CString strText;
	CWnd *pWnd = GetDlgItem(IDC_WRITE);
	pWnd->GetWindowText(strText);

	pWnd->EnableWindow(FALSE);
	if (strText == _T("Cancel")) {
		m_bCancel = TRUE;
		return;
	}

	m_cmbImgLst.GetWindowText(strText);
	m_strImgPath = strText.Left(strText.ReverseFind(_T('\\')) + 1);
	m_bCancel = FALSE;
	CreateThread(&CDirectDiskDlg::WriteAllImages);
}

BOOL CDirectDiskDlg::Rescan()
{
	int i = 0;
	CString strPhyDrv;
	m_cmbPhyDrv.ResetContent();
	m_editOutput.SetWindowText(_T(""));
	OutputText(const_strLine);
	while (TRUE) {
		strPhyDrv.Format(_T("\\\\.\\PHYSICALDRIVE%d"), i++);
		CDisk disk(strPhyDrv);
		if (disk.IsValid()) {
			DISK_GEOMETRY dg;

			m_cmbPhyDrv.AddString(strPhyDrv);
			OutputText(_T("[")+strPhyDrv+_T("]"), COLOR_BLUE);
			disk.GetGeometry(&dg);
			strPhyDrv.Format(_T("DiskSize: %.2fGB, SectorSize: %dB, MediaType: %s\r\n"),
				((float)disk.GetTotalSectors() * disk.GetSectorSize() / GB), disk.GetSectorSize(),
				dg.MediaType == FixedMedia ? _T("FixedMedia") : dg.MediaType == RemovableMedia ? _T("RemovableMedia") : _T("Unknown"));
			OutputText(strPhyDrv);

			if (i == 1 || dg.MediaType == FixedMedia) {
				/* XXX: CDisk will skip the writing to local hard-disk */
				disk.SetReadOnly();
				OutputText(_T("(For security reason, DirectDisk will skip any writing to this disk!)"), COLOR_RED);
			}

			m_nTotalSectors = disk.GetTotalSectors();
			if (disk.Read(&m_mbr, sizeof(DISK_MBR))) {
				CString strPart;
				for (int i = 0; i < 4; i++) {
					float fSize = (float)m_mbr.Part[i].Part_TotalSectors * disk.GetSectorSize() / MB;
					strPart.Format(_T("Partition %d - fs: 0x%02x, start sector: %d, total sectors: %d"),
						i + 1, m_mbr.Part[i].Part_FileSystem, m_mbr.Part[i].Part_StartSector,
						m_mbr.Part[i].Part_TotalSectors);
					if (fSize > 1024) {
						fSize /= KB;
						strPart.Format(_T("%s, size: %.2fGB"), strPart, fSize);
					} else if (fSize > 0.005f) {
						strPart.Format(_T("%s, size: %.2fMB"), strPart, fSize);
					} else {
						strPart.Format(_T("%s, size: 0"), strPart);
					}
					OutputText(strPart);
				}
			}
		} else {
			break;
		}
	}
	OutputText(const_strLine);
	m_cmbPhyDrv.SetCurSel(i-2);
	m_ctlProgress.SetPos(0);
	Invalidate(FALSE);
	GetDlgItem(IDC_RESCAN)->EnableWindow(TRUE);
	return TRUE;
}

void CDirectDiskDlg::OnBnClickedRescan()
{
	GetDlgItem(IDC_RESCAN)->EnableWindow(FALSE);
	CreateThread(&CDirectDiskDlg::Rescan);
}

void CDirectDiskDlg::OnBnClickedBrowse()
{
	CFileDialog dlg(TRUE, _T("lst"), NULL, 0, _T("LST Files(*.lst)|*.lst||"), this);
	if (dlg.DoModal() == IDOK) {
		m_cmbImgLst.SetWindowText(dlg.GetPathName());
		AfxGetApp()->WriteProfileString(_T("Config"), _T("ImgLstFile"), dlg.GetPathName());

		CStdioFile file;
		if (file.Open(dlg.GetPathName(), CFile::modeRead)) {
			CString strFile;
			OutputText(const_strLine);
			while (file.ReadString(strFile)) {
				OutputText(strFile);
			}
			OutputText(const_strLine);
			file.Close();
		}
	}
}

void CDirectDiskDlg::OutputText(LPCTSTR lpszText, COLORREF crText)
{
	CString strText(lpszText);
	int nLen = strText.GetLength();
	if (nLen == 0 || (strText[nLen - 1] != '\r' && strText[nLen - 1] != '\n'))
	{
		strText += "\r\n";
	}
	m_editOutput.SetFocus();
	m_editOutput.SetSel(-1, -1);

	CHARFORMAT2 cf;
	cf.cbSize = sizeof(CHARFORMAT2);
	cf.dwMask = CFM_COLOR;
	cf.dwEffects = 0;
	cf.crTextColor = crText;
	m_editOutput.SetSelectionCharFormat(cf);

	m_editOutput.ReplaceSel(strText);
	m_editOutput.PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
}

void CDirectDiskDlg::OnSelchangedPhyDrv()
{
	CString strPhyDrv;
	m_cmbPhyDrv.GetWindowText(strPhyDrv);
	CDisk disk(strPhyDrv);
	m_nTotalSectors = disk.GetTotalSectors();
	if (disk.Read(&m_mbr, sizeof(DISK_MBR))) {
		Invalidate(FALSE);
	}
}

void CDirectDiskDlg::EnableAllBut(BOOL bEnable, UINT nID)
{
	// FIXME: Resource ID should be ordered from IDC_PHYDRV to IDC_WRITE (see Resource.h)
	for (UINT n = IDC_PHYDRV; n <= IDC_EJECT; n++) {
		if (n != nID)
			GetDlgItem(n)->EnableWindow(bEnable);
	}
	GetDlgItem(IDCANCEL)->EnableWindow(bEnable);
}

BOOL CDirectDiskDlg::OnDeviceChange(UINT nEventType, DWORD dwData)
{
	DEV_BROADCAST_HDR *dbh = (DEV_BROADCAST_HDR*)dwData;
	switch(nEventType) {
	case DBT_DEVICEARRIVAL:
	case DBT_DEVICEREMOVECOMPLETE:
		if (GetDlgItem(IDC_RESCAN)->IsWindowEnabled())
			OnBnClickedRescan();
		break;
	}
	return FALSE; 
}

void CDirectDiskDlg::OnBnClickedEject()
{
	CString strPhyDrv;
	GetDlgItem(IDC_PHYDRV)->GetWindowText(strPhyDrv);
	CDisk disk(strPhyDrv);
	if (disk.Eject()) {
		Rescan();
		OutputText(_T("Eject \"") + strPhyDrv + _T("\" done!"));
	} else {
		OutputText(_T("Eject \"") + strPhyDrv + _T("\" failed!"), COLOR_RED);
	}
}
